#include "Neuron.h"
#include "MathUtil.h"
#include "defs.h"
#include <QtCore/QString>
#include <QtXml/QDomElement>
#include <QtXml/QDomDocument>
#include <math.h>
#include <iostream>
namespace jcrada {

    Neuron::Neuron( int number_of_inputs ) :
    _number_of_inputs( number_of_inputs ),
    _inputs( new double[number_of_inputs] ),
    _weights( new double[number_of_inputs] ),
    _threshold( 0.0 ),
    _activation_function( AF_NONE ) {
        for (int i = 0; i < getNumberOfInputs(); ++i) {
            setWeight(i, 1.0);
            setInput(i, 0.0);
        }
    }

    Neuron::~Neuron( ) {
        delete [] _inputs;
        delete [] _weights;
    }

    double Neuron::output( ) const {
        double result = 0.0;
        for (int i = 0; i < getNumberOfInputs(); ++i) {
            result += getInput(i) * getWeight(i);
        }
        result += -1.0 * getThreshold();
        switch (getActivationFunction()) {
            case AF_NONE: break;
            case AF_TANH: result = tanh(result);
                break;
            case AF_SIGMOID: result = Math::sigmoid(result);
                break;
            default: assert(0);
        }
        return result;
    }

    Neuron* Neuron::clone( ) const {
        Neuron* result = new Neuron(getNumberOfInputs());
        result->setActivationFunction(getActivationFunction());
        result->setThreshold(getThreshold());
        for (int i = 0; i < result->getNumberOfInputs(); ++i) {
            result->setInput(i, getInput(i));
            result->setWeight(i, getWeight(i));
        }
        return result;
    }

    Neuron* Neuron::fromXml( QDomElement& element, bool* ok ) {
        int number_of_inputs = element.attribute("number-of-inputs", "0").toInt(ok);
        Neuron* result = new Neuron(number_of_inputs);
        result->setActivationFunction((eActivationFunction) element.attribute(
                "activation-function", QString::number(AF_NONE)).toInt(ok));
        result->setThreshold(element.attribute("threshold", "0.0").toDouble(ok));

        QDomNode child = element.firstChild();
        int i = 0, w = 0;
        while (!child.isNull()) {
            QDomElement e = child.toElement();
            if (e.tagName() == "input") {
                assert(i < number_of_inputs);
                result->setInput(i++, e.attribute("value", "0.0").toDouble(ok));
            } else if (e.tagName() == "weight") {
                assert(w < number_of_inputs);
                result->setWeight(w++, e.attribute("value", "1.0").toDouble(ok));
            }
            child = child.nextSibling();
        }
        if (!*ok) {
            BDEBUG("Error in Neuron::fromXml");
        }
        return result;
    }

    void Neuron::toXml( QDomDocument& xml, QDomElement* parent ) const {
        QDomElement neuron = xml.createElement("Neuron");
        neuron.setAttribute("threshold", getThreshold());
        neuron.setAttribute("activation-function", getActivationFunction());
        neuron.setAttribute("number-of-inputs", getNumberOfInputs());
        for (int i = 0; i < getNumberOfInputs(); ++i) {
            QDomElement input = xml.createElement("input");
            QDomElement weight = xml.createElement("weight");
            input.setAttribute("value", getInput(i));
            weight.setAttribute("value", getWeight(i));
            neuron.appendChild(input);
            neuron.appendChild(weight);
        }
        if (parent) {
            parent->appendChild(neuron);
        } else {
            xml.appendChild(neuron);
        }
    }

    int Neuron::main( int argc, char** argv ) {
        (void) argc;
        (void) argv;
        Neuron * n = new Neuron(5);
        for (int i = 0; i < n->getNumberOfInputs(); ++i) {
            n->setInput(i, i);
            n->setWeight(i, i * 2);
        }
        n->setThreshold(1.5);
        n->setActivationFunction(AF_TANH);
        QDomDocument doc;
        n->toXml(doc);
        QString xml = doc.toString();
        std::cout << xml.toStdString() << std::endl;
        doc.clear();
        QString* error_msg = new QString();
        int * error_line = new int();
        int * error_col = new int();

        doc.setContent(xml, error_msg, error_line, error_col);
        std::cout << "Error: " << error_msg->toStdString() << " at [" << *error_line << " , "
                << *error_col << "]" << std::endl;
        delete error_msg;
        delete error_line;
        delete error_col;
        delete n;
        QDomElement neuronxml = doc.firstChildElement("Neuron");
        doc.clear();
        n->toXml(doc);
        xml = doc.toString();
        std::cout << xml.toStdString() << std::endl;
        return 0;
    }
}
